/**
 * ISSUE:
 *      1. there is no multi-mount check, and the umount is the same
 */

#include "driver.h"
#include "assert.h"
#include "mstring.h"
#include "matrix.h"

/* the driver list (all drivers) */
mx_carray_t drvbase;
/* global mutex for both device and driver */
mx_mutex_t glbmutex;

/* extern from device.c */
extern mx_carray_t ndevbase;

/**
 * mount a driver to system
 * @param pdrv: the descriptor of driver
 * @param name: the name of driver
 * @param ops: the unify operations
 * @param cusops: the custom operations
 * @param events: the events for mount/umount
 * @param data: the data of driver, can be null
 * @return: void
 */
void mx_driver_mount (mx_driver_pt pdrv, mx_string_pt name, mx_driver_ops_pt ops,
                      mx_void_pt cusops, mx_driver_events_pt events, mx_void_pt data)
{
    assert_false(pdrv == mx_null);
    assert_false(name == mx_null);
    assert_false(ops == mx_null);
    assert_false(events == mx_null);
    assert_false(events->onlinkup == mx_null || events->onlinkdown == mx_null);

    pdrv->name = name;
    pdrv->ops = ops;
    pdrv->cusops = cusops;
    pdrv->data = data;
    pdrv->events = events;
    mx_carray_node_init(&pdrv->node);
    mx_carray_init(&pdrv->devbase);

    /* call the onmount event */
    if (pdrv->events->onmount != mx_null)
    {
        pdrv->events->onmount(pdrv);
    }

    mx_mutex_enter(&glbmutex);

    /* match device */
    mx_dlist_pt tmpnext;
    for (mx_dlist_pt next = ((&ndevbase)->dlist).next; next != &(&ndevbase)->dlist; next = tmpnext)
    {
        tmpnext = next->next;
        mx_carray_node_pt n = container(next, mx_carray_node_t, dlist);;
        mx_device_pt pdev = container(n, mx_device_t, node);;
        if (mx_strcmp(name, pdev->data->drvname) == 0)
        {
            mx_bool_t res = pdrv->events->onlinkup(pdrv, pdev);
            if (res)
            {
                pdev->seek = 0;
                pdev->rely.drv = (mx_void_pt)pdrv;
                mx_carray_append(&pdrv->devbase, &pdev->node);
            }
        }
    }

    /* insert to the driver chain */
    mx_carray_append(&drvbase, &pdrv->node);

    mx_mutex_leave(&glbmutex);
}

/**
 * umount a driver from system
 * @param pdrv: the descriptor of driver
 * @return: the result of umount operation (mx_err_t)
 */
mx_err_t mx_driver_umount (mx_driver_pt pdrv)
{
    assert_false(pdrv == mx_null);

    mx_err_t ret = EOK;

    mx_mutex_enter(&glbmutex);  // avoid device mount

    /* check whether a device who blow this driver is be in use */
    mx_bool_t isbusy = mx_false;
    mx_carray_foreach(&pdrv->devbase, n, {
        mx_device_pt pdev = container(n, mx_device_t, node);
        mx_bool_t lock = mx_mutex_tryenter(&pdev->mutex);
        if (!lock || pdev->isopen)
        {
            isbusy = mx_true;
            break;
        }
    });

    if (!isbusy)
    {
        /* link down all device */
        for (mx_carray_node_pt n = mx_carray_get(&pdrv->devbase, 0);    // can't use mx_carray_foreach because node can't be remove in it
            n != mx_null;
            n = mx_carray_get(&pdrv->devbase, 0))
        {
            mx_device_pt pdev = container(n, mx_device_t, node);
            /* call the link down event */
            pdrv->events->onlinkdown(pdrv, pdev);
            pdev->rely.drv = mx_null;
            /* move dev from pdrv->devbase to devbase(device.c) */
            mx_carray_append(&ndevbase, n);
            /* unlock */
            mx_mutex_leave(&pdev->mutex);
        }

        /* call the umount event */
        if (pdrv->events->onumount != mx_null)
        {
            pdrv->events->onumount(pdrv);
        }

        /* remove driver from driver chain */
        mx_carray_remove(&pdrv->node);
    }
    else
    {
        ret = EDEV_BUSY;
        mx_carray_foreach(&pdrv->devbase, n, {
            mx_device_pt pdev = container(n, mx_device_t, node);
            /* unlock */
            mx_mutex_leave(&pdev->mutex);
        });
    }

    mx_mutex_leave(&glbmutex);

    return ret;
}

/**
 * get the count of driver in system
 * @param: void
 * @return: driver count (mx_usize_t)
 */
mx_usize_t mx_driver_count ()
{
    mx_usize_t count = 0;

    mx_mutex_enter(&glbmutex);
    count = mx_carray_count(&drvbase);
    mx_mutex_leave(&glbmutex);

    return count;
}

/**
 * get driver by name
 * @param name: the driver name
 * @return: the descriptor of driver (mx_driver_pt)
 */
mx_driver_pt mx_driver_get (mx_string_pt name)
{
    mx_driver_pt ret = mx_null;

    mx_mutex_enter(&glbmutex);
    mx_carray_foreach(&drvbase, n, {
        mx_driver_pt pdrv = container(n, mx_driver_t, node);
        if (mx_strcmp(name, pdrv->name) == 0)
        {
            ret = pdrv;
            break;
        }
    });
    mx_mutex_leave(&glbmutex);

    return ret;
}

static void driver_init ()
{
    mx_carray_init(&drvbase);
    mx_mutex_init(&glbmutex);
}
MX_BOARDINIT_EXPORT(driver_init, 0);
